﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LightCAD.Core
{
    public class ElementCollection : Collection<LcElement>
    {
        private const int DefaultThreshold = 0;
        private readonly IEqualityComparer<long> comparer; // Do not rename (binary serialization)
        private Dictionary<long, LcElement>? dict; // Do not rename (binary serialization)
        private int keyCount; // Do not rename (binary serialization)
        private readonly int threshold; // Do not rename (binary serialization)

        public ElementCollection() : this(null, DefaultThreshold)
        {
        }

        public ElementCollection(IEqualityComparer<long>? comparer) : this(comparer, DefaultThreshold)
        {
        }

        protected ElementCollection(IEqualityComparer<long>? comparer, int dictionaryCreationThreshold)
            : base(new List<LcElement>()) // Be explicit about the use of List<T> so we can foreach over
                                          // Items internally without enumerator allocations.
        {
            if (dictionaryCreationThreshold < -1)
            {
                throw new ArgumentOutOfRangeException(nameof(dictionaryCreationThreshold), "");// SR.ArgumentOutOfRange_InvalidThreshold);
            }

            this.comparer = comparer ?? EqualityComparer<long>.Default;
            threshold = dictionaryCreationThreshold == -1 ? int.MaxValue : dictionaryCreationThreshold;
        }

        public IEqualityComparer<long> Comparer => comparer;

        public LcElement this[int index]
        {
            get
            {
                return Items[index];
            }
        }
        public LcElement this[long key]
        {
            get
            {
                LcElement item;
                if (TryGetValue(key, out item!))
                {
                    return item;
                }

                throw new KeyNotFoundException("");//SR.Format(SR.Arg_KeyNotFoundWithKey, key.ToString())
            }
        }

        public bool Contains(long key)
        {
            ArgumentNullException.ThrowIfNull(key);

            if (dict != null)
            {
                return dict.ContainsKey(key);
            }

            foreach (LcElement item in Items)
            {
                if (comparer.Equals(GetKeyForItem(item), key))
                {
                    return true;
                }
            }

            return false;
        }

        public bool TryGetValue(long key, [MaybeNullWhen(false)] out LcElement item)
        {
            ArgumentNullException.ThrowIfNull(key);

            if (dict != null)
            {
                return dict.TryGetValue(key, out item!);
            }

            foreach (LcElement itemInItems in Items)
            {
                long keyInItems = GetKeyForItem(itemInItems);
                if (keyInItems != null && comparer.Equals(key, keyInItems))
                {
                    item = itemInItems;
                    return true;
                }
            }

            item = default;
            return false;
        }

        private bool ContainsItem(LcElement item)
        {
            long key;
            if ((dict == null) || ((key = GetKeyForItem(item)) == null))
            {
                return Items.Contains(item);
            }

            LcElement itemInDict;
            if (dict.TryGetValue(key, out itemInDict!))
            {
                return EqualityComparer<LcElement>.Default.Equals(itemInDict, item);
            }

            return false;
        }

        public bool Remove(long key)
        {
            ArgumentNullException.ThrowIfNull(key);

            if (dict != null)
            {
                LcElement item;
                return dict.TryGetValue(key, out item!) && Remove(item);
            }

            for (int i = 0; i < Items.Count; i++)
            {
                if (comparer.Equals(GetKeyForItem(Items[i]), key))
                {
                    RemoveItem(i);
                    return true;
                }
            }

            return false;
        }

        public IDictionary<long, LcElement>? Dictionary => dict;

        protected void ChangeItemKey(LcElement item, long newKey)
        {
            if (!ContainsItem(item))
            {
                throw new ArgumentException();//SR.Argument_ItemNotExist, nameof(item)
            }

            long oldKey = GetKeyForItem(item);
            if (!comparer.Equals(oldKey, newKey))
            {
                if (newKey != null)
                {
                    AddKey(newKey, item);
                }
                if (oldKey != null)
                {
                    RemoveKey(oldKey);
                }
            }
        }

        protected override void ClearItems()
        {
            base.ClearItems();
            dict?.Clear();
            keyCount = 0;
        }

        protected long GetKeyForItem(LcElement item)
        {
            return item.Id;
        }

        protected override void InsertItem(int index, LcElement item)
        {
            long key = GetKeyForItem(item);
            if (key != null)
            {
                AddKey(key, item);
            }

            base.InsertItem(index, item);
        }

        protected override void RemoveItem(int index)
        {
            long key = GetKeyForItem(Items[index]);
            if (key != null)
            {
                RemoveKey(key);
            }

            base.RemoveItem(index);
        }

        protected override void SetItem(int index, LcElement item)
        {
            long newKey = GetKeyForItem(item);
            long oldKey = GetKeyForItem(Items[index]);

            if (comparer.Equals(oldKey, newKey))
            {
                if (newKey != null && dict != null)
                {
                    dict[newKey] = item;
                }
            }
            else
            {
                if (newKey != null)
                {
                    AddKey(newKey, item);
                }

                if (oldKey != null)
                {
                    RemoveKey(oldKey);
                }
            }

            base.SetItem(index, item);
        }

        private void AddKey(long key, LcElement item)
        {
            if (dict != null)
            {
                dict.Add(key, item);
            }
            else if (keyCount == threshold)
            {
                CreateDictionary();
                dict!.Add(key, item);
            }
            else
            {
                if (Contains(key))
                {
                    throw new ArgumentException("", nameof(key));//SR.Format(SR.Argument_AddingDuplicate, key)
                }

                keyCount++;
            }
        }

        private void CreateDictionary()
        {
            dict = new Dictionary<long, LcElement>(comparer);
            foreach (LcElement item in Items)
            {
                long key = GetKeyForItem(item);
                if (key != null)
                {
                    dict.Add(key, item);
                }
            }
        }

        private void RemoveKey(long key)
        {
            Debug.Assert(key != null, "key shouldn't be null!");
            if (dict != null)
            {
                dict.Remove(key);
            }
            else
            {
                keyCount--;
            }
        }
    }
}
